const dayjs = require('dayjs');
const { get, compact } = require('lodash');
const assert = require('assert');
const Decimal = require('decimal.js');
const { generateTransactionId, validConfig, toNumber, isEmpty } = require('../utils');
const { request } = require('../misc/ows');

const {
	resort,
	chainCode,
	username,
	password,
	cardType,
	stationID,
	OriginEntityID,
	OriginSystemType,
	DestinationEntityID,
	DestinationSystemType
} = validConfig();

const RoomStatusMessage = {
	'NO RECORDS FOUND FOR REQUEST CRITERIA': '对不起，暂时没有可选择的房间，请稍后再试'
}

function getDate(dateString) {
	if (dateString) {
		return dateString.substr(0, 10);
	}

	return dateString;
}

function resolveUniqueID(UniqueID) {
	const item = {
		UniqueID: get(UniqueID, '_', ''),
		type: get(UniqueID, '$.type', ''), // The possible values are EXTERNAL, INTERNAL, CANCELLATIONEXTERNAL, and CANCELLATIONINTERNAL.
		source: get(UniqueID, '$.source', '')
	}

	return item;
}

function resolveUniqueIDList(UniqueIDList) {
	if (Array.isArray(UniqueIDList)) {
		return UniqueIDList.map(item => resolveUniqueID(item));
	}

	return [];
}

function resolvePersonName(PersonName) {
	return {
		nameTitle: get(PersonName, 'c:nameTitle.0', ''),
		firstName: get(PersonName, 'c:firstName.0', ''),
		lastName: get(PersonName, 'c:lastName.0', ''),
		middleName: get(PersonName, 'c:middleName.0', ''),
		nameSuffix: get(PersonName, 'c:nameSuffix.0', ''), // 尊敬的吴先生
	}
}

function resolveNativeName(NativeName) {
	return {
		languageCode: get(NativeName, '$.languageCode', ''),
		...resolvePersonName(NativeName)
	};
}

function resolvePhone(Phone) {
	const item = {};
	Object.assign(item, get(Phone, '$'));
	item.PhoneNumber = get(Phone, 'c:PhoneNumber.0');

	return item;
}

function resolveProfile(Profile) {
	const item = {
		languageCode: get(Profile, '$.languageCode'),
		nameID: get(Profile, 'ProfileIDs.0.c:UniqueID.0._'),
	};

	const PersonName = get(Profile, 'Customer.0.PersonName.0');
	item.PersonName = resolvePersonName(PersonName);

	const NativeName = get(Profile, 'Customer.0.NativeName.0');
	item.NativeName = resolveNativeName(NativeName);

	const Phone = get(Profile, 'Phones.0.NamePhone.0');
	item.Phone = resolvePhone(Phone);

	return item;
}

function resolveGuestCount(GuestCount) {
	const item = {};
	if (Array.isArray(GuestCount)) {
		for (const e of GuestCount) {
			item[e.$.ageQualifyingCode] = e.$.count;
		}
	}
	return item;
}

function resolveRoomRate(RoomRate) {
	const item = {};
	Object.assign(item, get(RoomRate, '$'));
	item.Rate = {
		value: toNumber(get(RoomRate, 'hc:Rates.0.hc:Rate.0.hc:Base.0._'))
	}
	Object.assign(item.Rate, get(RoomRate, 'hc:Rates.0.hc:Rate.0.hc:Base.0.$'));
	return item;
}

function resolveReservationID(UniqueIDs) {
	const item = {};
	if (Array.isArray(UniqueIDs)) {
		UniqueIDs.map(e => {
			const UniqueID = resolveUniqueID(e);
			if (UniqueID.source === 'RESVID') {
				Object.assign(item, { resvNameId: UniqueID.UniqueID });
			} else if (UniqueID.source === 'LEGNUMBER') {
				Object.assign(item, { legNumber: UniqueID.UniqueID });
			} else if (!UniqueID.source) {
				Object.assign(item, { confirmNumber: UniqueID.UniqueID });
			}
		});
	}

	return item;
}

function resolveGuaranteeCreditCard(GuaranteeCreditCard) {
	const item = {
		cardType: get(GuaranteeCreditCard, '$.cardType'), // string   The type of payment card (i.e. credit, Debit, voucher, etc.)
		otherCardType: '', // string   Not used in this context.
		chipAndPin: false, // boolean   Indicates if the calling system can handle a chip and pin device. For example, when called from a Kiosk system this flag must be set to true so that the guest can swipe the card on a chip and pin device when the credit card vendor makes it active.  When called from the web, this flag needs to be set to false as the guest cannot swipe a credit card online.
		cardCode: '', // string   The credit card vendor code (i.e. VA, AX, etc.).
		cardHolderName: '', // string   The name on the face of the card.
		cardNumber: get(GuaranteeCreditCard, 'c:cardNumber.0'), // string   The credit card number. Asian region will have local credit card with length of up to 30 numbers.
		VaultedCardData: null, // VaultedCardType   The vaulted card type.
		seriesCode: '', // string   The credit card series code.
		Track2: '', // string   Credit card track 2 data.
		effectiveDate: null, // date   The first date that the card is active.
		expirationDate: get(GuaranteeCreditCard, 'c:expirationDate.0'), // date   The expiration date of the card.
		EncryptedSwipe: null, // EncryptedSwipe   The encrypted swipe data for the credit card.
	}

	return item;
}

function resolveGuaranteesAccepted(GuaranteesAccepted) {
	return {
		agentPhone: null,
		GuaranteesAccepted: resolveGuaranteeCreditCard(get(GuaranteesAccepted, 'hc:GuaranteeCreditCard.0')),
		GuaranteeCompany: null,
		GuaranteeTravelAgent: null,
	};
}

function resolveReservationGuarantee(ReservationGuarantee) {
	const item = {
		...resolveGuaranteesAccepted(get(ReservationGuarantee, 'hc:GuaranteesAccepted.0.hc:GuaranteeAccepted.0'))
	};
	Object.assign(item, get(ReservationGuarantee, '$'));

	return item;
}

function resolveShareReservationDetail(ShareReservationDetail) {
	const item = {
		primary: get(ShareReservationDetail, '$.primary'),
		...resolveReservationID(get(ShareReservationDetail, 'r:UniqueIDList.0.c:UniqueID')),
		Profile: resolveProfile(get(ShareReservationDetail, 'r:Profile.0')),
		...resolveGuestCount(get(ShareReservationDetail, 'r:GuestCounts.0.hc:GuestCount')),
		...resolveReservationGuarantee(get(ShareReservationDetail, 'r:ReservationGuarantee.0'))
	};

	return item;
}

function resolveShareReservationDetails(ShareReservationDetails) {
	if (Array.isArray(ShareReservationDetails)) {
		return ShareReservationDetails.map(e => resolveShareReservationDetail(e));
	}

	return [];
}

function resolveResGuest(ResGuest) {
	const item = {
		Profile: resolveProfile(get(ResGuest, 'r:Profiles.0.Profile.0'))
	};
	Object.assign(item, get(ResGuest, '$'));

	return item;
}

function resolveFullName(reservation) {
	const data = {
		lastName: '',
		firstName: '',
		fullName: ''
	};

  if (Array.isArray(reservation.ResGuests) && reservation.ResGuests.length > 0) {
		data.lastName = get(reservation, 'ResGuests.0.Profile.PersonName.lastName', '');
    data.firstName = get(reservation, 'ResGuests.0.Profile.PersonName.firstName', '')
    const names = [
      data.lastName,
			data.firstName
    ]
    data.fullName = compact(names).join(', ');
  }

  return data;
}

function resolveAltName (reservation) {
  if (Array.isArray(reservation.ResGuests) && reservation.ResGuests.length > 0) {
    const names = [
      get(reservation, 'ResGuests.0.Profile.NativeName.lastName'),
      get(reservation, 'ResGuests.0.Profile.NativeName.firstName')
    ]
    return compact(names).join('')
  }

  return ''
}

function resolveAmount (element) {
	const el = {};
  if (Array.isArray(element) && element.length > 0) {
    Object.assign(el, get(element, '0.$'));
		el.value = toNumber(get(element, '0._', 0));
  }

  return el
}

function formatReservation(HotelReservation) {
	const Reservation = {};
	Object.assign(Reservation, get(HotelReservation, '$'));
	const UniqueID = get(HotelReservation, 'r:UniqueIDList.0.c:UniqueID');
	const ShareReservations = get(HotelReservation, 'r:ShareReservations');
	const ReservationPayments = get(HotelReservation, 'r:ReservationPayments');
	const RoomStay = get(HotelReservation, 'r:RoomStays.0.hc:RoomStay.0');
	const ResGuests = get(HotelReservation, 'r:ResGuests');
	const Miscellaneous = get(HotelReservation, 'r:Miscellaneous.0');

	Object.assign(Reservation, resolveReservationID(UniqueID));

	if (Array.isArray(ShareReservations)) {
		Reservation.ShareReservationDetails = resolveShareReservationDetails(get(ShareReservations, '0.r:ShareReservationDetails'));
	}

	if (RoomStay) {
		Reservation.RoomStay = {};
		const RatePlans = get(RoomStay, 'hc:RatePlans.0');
		const RoomTypes = get(RoomStay, 'hc:RoomTypes.0');
		const RoomRates = get(RoomStay, 'hc:RoomRates.0');
		const GuestCounts = get(RoomStay, 'hc:GuestCounts.0');
		const TimeSpan = get(RoomStay, 'hc:TimeSpan.0');
		const Guarantee = get(RoomStay, 'hc:Guarantee');
		const Payment = get(RoomStay, 'hc:Payment');
		const CreditCardDeposit = get(RoomStay, 'hc:CreditCardDeposit');
		const CancelTerm = get(RoomStay, 'hc:CancelTerm');
		const HotelReference = get(RoomStay, 'hc:HotelReference');
		const Total = get(RoomStay, 'hc:Total.0');
		const CurrentBalance = get(RoomStay, 'hc:CurrentBalance.0');
		const ResGuestRPHs = get(RoomStay, 'hc:ResGuestRPHs');
		const Comments = get(RoomStay, 'hc:Comments');
		const SpecialRequests = get(RoomStay, 'hc:SpecialRequests');
		const Packages = get(RoomStay, 'hc:Packages');
		const DailyChargePoints = get(RoomStay, 'hc:DailyChargePoints');
		const ExpectedCharges = get(RoomStay, 'hc:ExpectedCharges.0');

		if (RatePlans) {
			Reservation.RoomStay.RatePlan = {};
			const RatePlan = get(RatePlans, 'hc:RatePlan.0');
			Object.assign(Reservation.RoomStay.RatePlan, get(RatePlan, '$'));
			Reservation.RoomStay.RatePlan.RatePlanDescription = get(RatePlan, 'hc:RatePlanDescription.0.hc:Text.0');
			const DepositRequired = get(RatePlan, 'hc:DepositRequired.0');
			if (DepositRequired) {
				Reservation.RoomStay.RatePlan.DepositRequired = {
					DepositAmount: resolveAmount(get(DepositRequired, 'hc:DepositAmount')),
					DueDate: get(DepositRequired, 'hc:DueDate.0'),
					DepositDueAmount: resolveAmount(get(DepositRequired, 'hc:DepositDueAmount'))
				}
			}
		}

		if (RoomTypes) {
			Reservation.RoomStay.RoomType = {};
			const RoomType = get(RoomTypes, 'hc:RoomType.0');
			Object.assign(Reservation.RoomStay.RoomType, get(RoomType, '$'));
			Reservation.RoomStay.RoomType.RoomTypeDescription = get(RoomType, 'hc:RoomTypeDescription.0.hc:Text.0');
			Reservation.RoomStay.RoomType.RoomTypeShortDescription = get(RoomType, 'hc:RoomTypeShortDescription.0.hc:Text.0');
			Reservation.RoomStay.RoomType.RoomToChargeDescription = get(RoomType, 'hc:RoomToChargeDescription.0.hc:Text.0');
			Reservation.RoomStay.RoomType.RoomToChargeShortDescription = get(RoomType, 'hc:RoomToChargeShortDescription.0.hc:Text.0');
			Reservation.RoomStay.RoomType.RoomNumber = get(RoomType, 'hc:RoomNumber.0', '');
			Reservation.RoomStay.RoomType.numberOfUnits = toNumber(get(RoomType, '$.numberOfUnits', 0)); // 房间数量，=1 时才能选房
		}

		if (RoomRates) {
			Reservation.RoomStay.RoomRate = resolveRoomRate(get(RoomRates, 'hc:RoomRate.0'));
		}

		if (GuestCounts) {
			Reservation.RoomStay.GuestCount = resolveGuestCount(get(GuestCounts, 'hc:GuestCount'));
		}

		if (TimeSpan) {
			Reservation.RoomStay.StartDate = getDate(get(TimeSpan, 'hc:StartDate.0'));
			Reservation.RoomStay.EndDate = getDate(get(TimeSpan, 'hc:EndDate.0'));
			Reservation.RoomStay.Nights = dayjs(Reservation.RoomStay.EndDate).diff(dayjs(Reservation.RoomStay.StartDate), 'days');
		}

		if (Total) {
			Reservation.RoomStay.Total = {
				value: toNumber(get(Total, '_')),
				currencyCode: get(Total, '$.currencyCode'),
				decimals: get(Total, '$.decimals'),
			}
		}

		if (CurrentBalance) {
			Reservation.RoomStay.CurrentBalance = {
				value: toNumber(get(CurrentBalance, '_')),
				currencyCode: get(Total, '$.currencyCode'),
				decimals: get(Total, '$.decimals'),
			}
		}

		if (Array.isArray(ResGuestRPHs) && ResGuestRPHs.length > 0) {
			Reservation.ResGuestRPHs = [];
			for (const item of ResGuestRPHs) {
				Reservation.ResGuestRPHs.push({ RPH: get(item, 'hc:ResGuestRPH.0.$.RPH') });
			}
		}

		if (ExpectedCharges) {
			Reservation.RoomStay.ExpectedCharges = {};
			Object.assign(Reservation.RoomStay.ExpectedCharges, get(ExpectedCharges, '$'));
			const ChargesForPostingDate = get(ExpectedCharges, 'hc:ChargesForPostingDate');
			if (Array.isArray(ChargesForPostingDate)) {
				Reservation.RoomStay.ExpectedCharges.ChargesForPostingDate = [];
				for (const item of ChargesForPostingDate) {
					const el = {
						PostingDate: get(item, '$.PostingDate'),
						TotalCharges: 0,
						RoomRateAndPackages: {},
						TaxesAndFees: {}
					};

					const RoomRateAndPackages = get(item, 'hc:RoomRateAndPackages.0');
					const TaxesAndFees = get(item, 'hc:TaxesAndFees.0');
					Object.assign(el.RoomRateAndPackages, get(RoomRateAndPackages, '$'));
					Object.assign(el.TaxesAndFees, get(TaxesAndFees, '$'));
					const Charges = get(RoomRateAndPackages, 'hc:Charges.0');
					const TaxCharges = get(TaxesAndFees, 'hc:Charges.0');

					el.RoomRateAndPackages.Charges = {
						Description: get(Charges, 'hc:Description.0'),
						Amount: {
							value: toNumber(get(Charges, 'hc:Amount.0._')),
							currencyCode: get(Charges, 'hc:Amount.0.$.currencyCode'),
							decimals: get(Charges, 'hc:Amount.0.$.decimals'),
						}
					}

					el.TaxesAndFees.Charges = {
						Description: get(TaxCharges, 'hc:Description.0'),
						Amount: {
							value: toNumber(get(TaxCharges, 'hc:Amount.0._')),
							currencyCode: get(TaxCharges, 'hc:Amount.0.$.currencyCode'),
							decimals: get(TaxCharges, 'hc:Amount.0.$.decimals'),
						},
						CodeType: get(TaxCharges, 'hc:CodeType.0'),
						Code: get(TaxCharges, 'hc:Code.0'),
					}

					el.TotalCharges = new Decimal(get(el, 'RoomRateAndPackages.TotalCharges', 0)).plus(new Decimal(get(el, 'TaxesAndFees.TotalCharges', 0))).toNumber();

					Reservation.RoomStay.ExpectedCharges.ChargesForPostingDate.push(el);
				}
			}
		}
	}

	if (Array.isArray(ResGuests)) {
		Reservation.ResGuests = [];
		for (const item of ResGuests) {
			const ResGuest = get(item, 'r:ResGuest.0');

			Reservation.ResGuests.push(resolveResGuest(ResGuest));
		}

		if (Reservation.ResGuests.length > 0) {
			const Phone = get(Reservation.ResGuests[0], 'Profile.Phone', {});
			if (Phone.phoneRole === 'PHONE') {
				Reservation.PhoneNumber = Phone.PhoneNumber;
			}
		}
	}

	if (Miscellaneous) {
		Reservation.Miscellaneous = {};
		Reservation.Miscellaneous.PartyCode = get(Miscellaneous, 'r:PartyCode.0', '');
	}

	const { lastName, firstName, fullName } = resolveFullName(Reservation);
	Reservation.lastName = lastName;
	Reservation.firstName = firstName;
	Reservation.fullName = fullName;
	Reservation.altName = resolveAltName(Reservation);

	return Reservation;
}

function formatReservations(HotelReservations) {
	if (Array.isArray(HotelReservations)) {
		return HotelReservations.map((HotelReservation) => formatReservation(HotelReservation));
	}

	return HotelReservations;
}

function formatHousekeepingRoomStatus(HouseKeepingRoomStatus) {
	if (Array.isArray(HouseKeepingRoomStatus)) {
		const rows = [];
		for (const item of HouseKeepingRoomStatus) {
			const row = {};
			Object.assign(row, get(item, '$'));
			if (Array.isArray(item.Features)) {
				const Features = get(item, 'Features.0.Features', []);
				row.Features = Features.map((e) => e.$)
			}
			rows.push(row);
		}

		return rows;
	}

	return [];
}

function resolveOWSResult(Result) {
	if (get(Result, '0.$.resultStatusFlag') !== 'SUCCESS') {
		throw new Error(get(Result, '0.c:Text.0.c:TextElement.0', get(Result, '0.c:OperaErrorCode.0', get(Result, '0.hc:GDSError.0._', JSON.stringify(Result)))));
	}
}

class OWS {
	async fetchBooking(req, res, next) {
		const { responseType } = req.query;
		const confirmNumber = get(req, 'params.confirmNumber', '');

		const timeStamp = dayjs().format();
		const transactionID = generateTransactionId();

		try {
			if (!/^[0-9]*$/.test(confirmNumber)) {
				throw new Error('无效的确认号');
			}

			const options = {
				cupath: '/Reservation.asmx',
				headers: {
					SOAPAction: 'http://webservices.micros.com/ows/5.1/Reservation.wsdl#FetchBooking'
				},
				xml: `<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
			<soap:Header>
					<OGHeader transactionID="${transactionID}" primaryLangID="E" timeStamp="${timeStamp}" xmlns="http://webservices.micros.com/og/4.3/Core/">
							<Origin entityID="${OriginEntityID}" systemType="${OriginSystemType}" />
							<Destination entityID="${DestinationEntityID}" systemType="${DestinationSystemType}" />
					</OGHeader>
			</soap:Header>
			<soap:Body>
					<FetchBookingRequest xmlns="http://webservices.micros.com/ows/5.1/Reservation.wsdl">
							<ConfirmationNumber type="INTERNAL">${confirmNumber}</ConfirmationNumber>
					</FetchBookingRequest>
			</soap:Body>
	</soap:Envelope>`};

			const { xmlData, jsonData } = await request(options);

			if (process.env.NODE_DEBUG) {
				console.dir(jsonData, { depth: null });
			};
			// console.dir(jsonData, { depth: null });

			if (responseType === 'xml') {
				res.sendOk(xmlData);
			} else if (responseType === 'json') {
				res.sendOk(jsonData);
			}

			const Result = get(jsonData, 'soap:Envelope.soap:Body.0.FetchBookingResponse.0.Result');
			resolveOWSResult(Result);

			const HotelReservation = get(jsonData, 'soap:Envelope.soap:Body.0.FetchBookingResponse.0.HotelReservation.0');
			const Reservation = formatReservation(HotelReservation);
			// console.dir(Reservation, { depth: null });

			res.sendOk(Reservation);
		} catch (e) {
			next(e);
		}
	}

	async assignRoom(req, res, next) {
		const { responseType } = req.query;
		const { resvNameId, room } = req.body;

		const timeStamp = dayjs().format();
		const transactionID = generateTransactionId();

		try {
			assert(resvNameId, '请选择预订');
			assert(room, '请选择房号');

			const options = {
				cupath: '/Reservation.asmx',
				headers: {
					SOAPAction: 'http://webservices.micros.com/ows/5.1/Reservation.wsdl#AssignRoom'
				},
				xml: `<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://webservices.micros.com/og/4.3/Common/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:r="http://webservices.micros.com/og/4.3/Reservation/" xmlns:hc="http://webservices.micros.com/og/4.3/HotelCommon/" xmlns:n="http://webservices.micros.com/og/4.3/Name/">
				<soap:Header>
					<OGHeader transactionID="${transactionID}" primaryLangID="E" timeStamp="${timeStamp}"  xmlns="http://webservices.micros.com/og/4.3/Core/">
						<Origin entityID="${OriginEntityID}" systemType="${OriginSystemType}" />
						<Destination entityID="${DestinationEntityID}" systemType="${DestinationSystemType}" />
						<Authentication>
							<UserCredentials>
								<UserName>${username}</UserName>                         
								<UserPassword>${password}</UserPassword>
								<Domain>${resort}</Domain>
							</UserCredentials>
						</Authentication>
					</OGHeader>
				</soap:Header>
				<soap:Body>
					<AssignRoomRequest xmlns="http://webservices.micros.com/ows/5.1/Reservation.wsdl">
						<HotelReference hotelCode="${resort}" chainCode="${chainCode}" />
						<ResvNameId type="INTERNAL" source="OPERA">${resvNameId}</ResvNameId>
						<RoomNoRequested>${room}</RoomNoRequested>
						<StationID>${stationID}</StationID>
					</AssignRoomRequest>
				</soap:Body>
			</soap:Envelope>`};

			const { xmlData, jsonData } = await request(options);

			if (process.env.NODE_DEBUG) {
				console.dir(jsonData, { depth: null });
			};

			if (responseType === 'xml') {
				res.sendOk(xmlData);
			} else if (responseType === 'json') {
				res.sendOk(jsonData);
			}

			const Result = get(jsonData, 'soap:Envelope.soap:Body.0.AssignRoomResponse.0.Result');
			resolveOWSResult(Result);

			const RoomNoAssigned = get(jsonData, 'soap:Envelope.soap:Body.0.AssignRoomResponse.0.RoomNoAssigned.0');
			res.sendOk(RoomNoAssigned);
		} catch (e) {
			next(e);
		}
	}

	async updateName(req, res, next) {
		const { responseType } = req.query;
		const { title, firstName, lastName, middleName, nameID, zhCode, xLastName, xFirstName } = req.body;

		const timeStamp = dayjs().format();
		const transactionID = generateTransactionId();

		const PersonName = [];
		const NativeName = [];
		let valid = false;

		if (title) {
			PersonName.push(`<c:nameTitle>${title}</c:nameTitle>`);
		}

		if (firstName) {
			PersonName.push(`<c:firstName>${firstName}</c:firstName>`);
		}

		if (lastName) {
			PersonName.push(`<c:lastName>${lastName}</c:lastName>`);
		}

		if (middleName) {
			PersonName.push(`<c:middleName>${middleName}</c:middleName>`);
		}

		if (PersonName.length > 0) {
			PersonName.unshift('<PersonName>');
			PersonName.push('</PersonName>');
			valid = true;
		}

		if (xFirstName) {
			NativeName.push(`<c:firstName>${xFirstName}</c:firstName>`);
		}

		if (xLastName) {
			NativeName.push(`<c:lastName>${xLastName}</c:lastName>`);
		}

		if (NativeName.length > 0) {
			if (zhCode) {
				NativeName.unshift(`<NativeName languageCode="${zhCode}">`);
			} else {
				NativeName.unshift('<NativeName>');
			}

			NativeName.push('</NativeName>');
			valid = true;
		}

		try {
			if (!/^[0-9]*$/.test(nameID)) {
				throw new Error('无效的档案ID');
			}

			if (!valid) {
				throw new Error('无可更新内容');
			}

			const options = {
				cupath: '/Name.asmx',
				headers: {
					SOAPAction: 'http://webservices.micros.com/ows/5.1/Name.wsdl#UpdateName'
				},
				xml: `<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
				<soap:Header>
					<OGHeader transactionID="${transactionID}" timeStamp="${timeStamp}" xmlns="http://webservices.micros.com/og/4.3/Core/">
						<Origin entityID="${OriginEntityID}" systemType="${OriginSystemType}" />
						<Destination entityID="${DestinationEntityID}" systemType="${DestinationSystemType}" />
						<Authentication>
							<UserCredentials>
								<UserName>${username}</UserName>                         
								<UserPassword>${password}</UserPassword>
								<Domain>${resort}</Domain>
							</UserCredentials>
						</Authentication>
					</OGHeader>
				</soap:Header>
				<soap:Body>
					<UpdateNameRequest xmlns:nm="http://webservices.micros.com/og/4.1/Name/" xmlns:c="http://webservices.micros.com/og/4.3/Common/" xmlns="http://webservices.micros.com/ows/5.1/Name.wsdl">
						<NameID type="INTERNAL">${nameID}</NameID>
						${PersonName.join(' ')}
						${NativeName.join(' ')}
					</UpdateNameRequest>
				</soap:Body>
			</soap:Envelope>`};

			const { xmlData, jsonData } = await request(options);

			if (process.env.NODE_DEBUG) {
				console.dir(jsonData, { depth: null });
			};

			if (responseType === 'xml') {
				res.sendOk(xmlData);
			} else if (responseType === 'json') {
				res.sendOk(jsonData);
			}

			const Result = get(jsonData, 'soap:Envelope.soap:Body.0.UpdateNameResponse.0.Result');
			resolveOWSResult(Result);

			res.sendOk({});
		} catch (e) {
			next(e);
		}
	}

	async fetchHouseKeepingRoomStatus(req, res, next) {
		const { responseType } = req.query;
		const { roomTypeCode, roomStatus, hkStatus, foStatus, reservationStatus } = req.body;

		const timeStamp = dayjs().format();
		const transactionID = generateTransactionId();

		try {
			assert(roomStatus, '请设置需要查询的ROOM_STATUS');
			assert(hkStatus, '请设置需要查询的HK_STATUS');
			assert(foStatus, '请设置需要查询的FO_STATUS');
			assert(reservationStatus, '请设置需要查询的RESERVATION_STATUS');

			const options = {
				cupath: '/Housekeeping.asmx',
				headers: {
					SOAPAction: 'http://webservices.micros.com/ows/5.1/HouseKeeping.wsdl#FetchHouseKeepingRoomStatus'
				},
				xml: `<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
				<soap:Header>
					<OGHeader transactionID="${transactionID}" primaryLangID="C" timeStamp="${timeStamp}" channelValidation="false" terminalID="${stationID}" xmlns="http://webservices.micros.com/og/4.3/Core/">
						<Origin entityID="${OriginEntityID}" systemType="${OriginSystemType}" />
						<Destination entityID="${DestinationEntityID}" systemType="${DestinationSystemType}" />
						<Authentication>
							<UserCredentials>
								<UserName>${username}</UserName>                         
								<UserPassword>${password}</UserPassword>
								<Domain>${resort}</Domain>
							</UserCredentials>
						</Authentication>
					</OGHeader>
				</soap:Header>
				<soap:Body>
        <FetchHouseKeepingRoomStatusRequest xmlns="http://webservices.micros.com/ows/5.1/HouseKeeping.wsdl">
            <HotelReference chainCode="${chainCode}" hotelCode="${resort}" />
            <Criteria>
                <RoomTypeList xmlns="http://webservices.micros.com/og/4.3/HouseKeeping/">
                    <Code xmlns="http://webservices.micros.com/og/4.3/Common/">${roomTypeCode}</Code>
                </RoomTypeList>
                <RoomStatusList xmlns="http://webservices.micros.com/og/4.3/HouseKeeping/">
                    ${roomStatus.split(',').map((code) => `<Code>${code}</Code>`).join(' ')}
                </RoomStatusList>
                <HKStatusList xmlns="http://webservices.micros.com/og/4.3/HouseKeeping/">
                    ${hkStatus.split(',').map((code) => `<Code>${code}</Code>`).join(' ')}
                </HKStatusList>
                <FOStatusList xmlns="http://webservices.micros.com/og/4.3/HouseKeeping/">
                    ${foStatus.split(',').map((code) => `<Code>${code}</Code>`).join(' ')}
                </FOStatusList>
                <ReservationStatusList xmlns="http://webservices.micros.com/og/4.3/HouseKeeping/">
                    ${reservationStatus.split(',').map((code) => `<Code>${code}</Code>`).join(' ')}
                </ReservationStatusList>
            </Criteria>
        </FetchHouseKeepingRoomStatusRequest>
    </soap:Body>
		</soap:Envelope>`};

			const { xmlData, jsonData } = await request(options);

			if (process.env.NODE_DEBUG) {
				console.dir(jsonData, { depth: null });
			};

			if (responseType === 'xml') {
				return res.sendOk(xmlData);
			} else if (responseType === 'json') {
				return res.sendOk(jsonData);
			}

			const Result = get(jsonData, 'soap:Envelope.soap:Body.0.FetchHouseKeepingRoomStatusResponse.0.Result');
			resolveOWSResult(Result);

			const HouseKeepingRoomStatus = get(jsonData, 'soap:Envelope.soap:Body.0.FetchHouseKeepingRoomStatusResponse.0.HouseKeepingRoomStatus.0.HKRoomStatus');
			res.sendOk(formatHousekeepingRoomStatus(HouseKeepingRoomStatus));
			
		} catch (e) {
			next(e);
		}
	}

	async makePayment(req, res, next) {
		const { responseType } = req.query;
		const { resvNameId, charge, out_trade_no, wx_transaction_id } = req.body;
		// out_trade_no 商户订单号
		// wx_transaction_id 微信支付订单号

		const timeStamp = dayjs().format();
		const transactionID = generateTransactionId();

		try {
			const postDate = dayjs().format('YYYY-MM-DD');
			const postTime = dayjs().format('HH:mm:ss');
			const options = {
				cupath: '/ResvAdvanced.asmx',
				headers: {
					SOAPAction: 'http://webservices.micros.com/ows/5.1/ResvAdvanced.wsdl#MakePayment'
				},
				xml: `<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <OGHeader primaryLangID="EN" timeStamp="${timeStamp}" transactionID="${transactionID}" channelValidation="false" xmlns="http://webservices.micros.com/og/4.3/Core/">
			<Origin entityID="${OriginEntityID}" systemType="${OriginSystemType}" />
			<Destination entityID="${DestinationEntityID}" systemType="${DestinationSystemType}" />
      <Authentication>
        <UserCredentials>
					<UserName>${username}</UserName>                         
					<UserPassword>${password}</UserPassword>
					<Domain>${resort}</Domain>
        </UserCredentials>
      </Authentication>
    </OGHeader>
  </soap:Header>
  <soap:Body>
    <MakePaymentRequest xmlns="http://webservices.micros.com/og/4.3/ResvAdvanced/" xmlns:com="http://webservices.micros.com/og/4.3/Common/">
      <Posting PostDate="${postDate}" PostTime="${postTime}" ShortInfo="Mini Program Short Info" LongInfo="商户单号${out_trade_no}" Charge="${charge}" StationID="${stationID}" UserID="${username}" FolioViewNo="1">
        <ReservationRequestBase>
            <HotelReference chainCode="${chainCode}" hotelCode="${resort}" />
          <ReservationID>
            <com:UniqueID type="INTERNAL" source="RESV_NAME_ID">${resvNameId}</com:UniqueID>
          </ReservationID>
        </ReservationRequestBase>
      </Posting>
      <CreditCardInfo>
        <CreditCard cardType="${cardType}">
        </CreditCard>
      </CreditCardInfo>
			<Reference>微信支付号${wx_transaction_id}</Reference>
    </MakePaymentRequest>
  </soap:Body>
</soap:Envelope>`};

			const { xmlData, jsonData } = await request(options);

			if (process.env.NODE_DEBUG) {
				console.dir(jsonData, { depth: null });
			};

			if (responseType === 'xml') {
				res.sendOk(xmlData);
			} else if (responseType === 'json') {
				res.sendOk(jsonData);
			}

			const Result = get(jsonData, 'soap:Envelope.soap:Body.0.MakePaymentResponse.0.Result');
			resolveOWSResult(Result);

			res.sendOk(Result);
		} catch (e) {
			next(e);
		}
	}

	async futureBookingSummary(req, res, next) {
		const { responseType } = req.query;
		let { dateType, startDate, endDate, reservationCount, summaryOnly, blockCode, companyName, 
			lastName, firstName, confirmNumber, resvNameId,
			getList, roomNumber, resvStatus, resvDisposition, roomClass, includePseudoRoom
		} = req.body;
		if (isEmpty(dateType)) {
			dateType = 'ARRIVAL_DATE'; // "BOOKED_DATE or ARRIVAL_DATE or DEPARTURE_DATE"
		}

		const timeStamp = dayjs().format();
		const transactionID = generateTransactionId();

		try {
			const FutureBookingSummaryRequestAttributes = [];
			if (!isEmpty(reservationCount)) {
				FutureBookingSummaryRequestAttributes.push(`numberOfReservationsToBeFetched="${reservationCount}"`);
			}

			if (isEmpty(summaryOnly)) {
				summaryOnly = false;
			}
			FutureBookingSummaryRequestAttributes.push(`summaryOnly="${summaryOnly}"`);

			const FutureBookingSummaryRequestElements = [];
			if (!isEmpty(reservationCount)) {
				FutureBookingSummaryRequestElements.push(`<res:numberOfReservationsToBeFetched>${reservationCount}</res:numberOfReservationsToBeFetched>`);
			}

			if (!isEmpty(lastName)) {
				FutureBookingSummaryRequestElements.push(`<res:LastName>${lastName}</res:LastName>`);
			}

			if (!isEmpty(firstName)) {
				FutureBookingSummaryRequestElements.push(`<res:FirstName>${firstName}</res:FirstName>`);
			}

			const AdditionalFiltersElements = [`<res1:HotelReference chainCode="${chainCode}" hotelCode="${resort}"/>`];
			if (!isEmpty(confirmNumber)) {
				AdditionalFiltersElements.push(`<res1:ConfirmationNumber type="INTERNAL">${confirmNumber}</res1:ConfirmationNumber>`);
			}

			if (!isEmpty(resvNameId)) {
				AdditionalFiltersElements.push(`<res1:ResvNameId type="INTERNAL" source="RESVID">${resvNameId}</res1:ResvNameId>`);
			}

			if (!isEmpty(companyName)) {
				AdditionalFiltersElements.push(`<res1:CompanyName>${companyName}</res1:CompanyName>`);
			}

			if (!isEmpty(blockCode)) {
				AdditionalFiltersElements.push(`<res1:BlockCode>${blockCode}</res1:BlockCode>`);
			}

			const AdditionalFiltersAttributes = []; // AdditionalFilters标签属性
			if (isEmpty(getList)) {
				getList = true;
			}
			AdditionalFiltersAttributes.push(`GetList="${getList}"`);

			if (!isEmpty(roomNumber)) {
				AdditionalFiltersAttributes.push(`RoomNumber="${roomNumber}"`);
			}

			// ReservationStatus="RESERVED or PROSPECT or NOSHOW or CANCELED or INHOUSE or CHECKEDOUT or CHANGED or WAITLISTED or PRECHECKEDIN or DUEOUT or DUEIN"
			if (isEmpty(resvStatus)) {
				resvStatus = 'DUEIN';
			}
			AdditionalFiltersAttributes.push(`ReservationStatus="${resvStatus}"`);

			if (isEmpty(resvDisposition)) {
				resvDisposition = 'NONE';
			}
			AdditionalFiltersAttributes.push(`ReservationDisposition="${resvDisposition}"`);
			

			if (!isEmpty(roomClass)) {
				AdditionalFiltersAttributes.push(`RoomClass="${roomClass}"`);
			}

			if (isEmpty(includePseudoRoom)) {
				includePseudoRoom = false;
			}
			AdditionalFiltersAttributes.push(`IncludePseudoRoom="${includePseudoRoom}"`);

			const options = {
				cupath: '/Reservation.asmx',
				headers: {
					SOAPAction: 'http://webservices.micros.com/ows/5.1/Reservation.wsdl#FutureBookingSummary'
				},
				xml: `<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:core="http://webservices.micros.com/og/4.3/Core/" xmlns:res="http://webservices.micros.com/ows/5.1/Reservation.wsdl" xmlns:hot="http://webservices.micros.com/og/4.3/HotelCommon/" xmlns:res1="http://webservices.micros.com/og/4.3/Reservation/" xmlns:com="http://webservices.micros.com/og/4.3/Common/">
    <soapenv:Header>
        <core:OGHeader transactionID="${transactionID}" timeStamp="${timeStamp}" primaryLangID="E" channelValidation="false" terminalID="${stationID}">
            <core:Origin entityID="${OriginEntityID}" systemType="${OriginSystemType}" />
            <core:Destination entityID="${DestinationEntityID}" systemType="${DestinationSystemType}" />
            <core:Authentication>
                <core:UserCredentials>
                    <core:UserName>${username}</core:UserName>
                    <core:UserPassword>${password}</core:UserPassword>
                    <core:Domain>${resort}</core:Domain>
                </core:UserCredentials>
            </core:Authentication>
        </core:OGHeader>
    </soapenv:Header>
    <soapenv:Body>
        <res:FutureBookingSummaryRequest ${FutureBookingSummaryRequestAttributes.join(' ')} xmlns:hc="http://webservices.micros.com/og/4.3/HotelCommon/" xmlns="http://webservices.micros.com/ows/5.1/Reservation.wsdl">
            <res:QueryDateRange dateType="${dateType}">
                <hot:StartDate>${startDate}</hot:StartDate>
                <hot:EndDate>${endDate}</hot:EndDate>
            </res:QueryDateRange>
            <res:AdditionalFilters ${AdditionalFiltersAttributes.join(' ')}>
								${AdditionalFiltersElements.join('\n')}
            </res:AdditionalFilters>
            ${FutureBookingSummaryRequestElements.join('\n')}
        </res:FutureBookingSummaryRequest>
    </soapenv:Body>
</soapenv:Envelope>`};

			const { xmlData, jsonData } = await request(options);

			if (process.env.NODE_DEBUG) {
				console.dir(jsonData, { depth: null });
			};

			if (responseType === 'xml') {
				return res.sendOk(xmlData);
			} else if (responseType === 'json') {
				return res.sendOk(jsonData);
			}

			const Result = get(jsonData, 'soap:Envelope.soap:Body.0.FutureBookingSummaryResponse.0.Result');

			resolveOWSResult(Result);

			const HotelReservations = get(jsonData, 'soap:Envelope.soap:Body.0.FutureBookingSummaryResponse.0.HotelReservations.0.r:HotelReservation');

			res.sendOk(formatReservations(HotelReservations));			
		} catch (e) {
			next(e);
		}
	}
}

module.exports = new OWS();
